//
// Copyright (C) 2008, 2009
// Barcelona Supercomputing Center - Centro Nacional de Supercomputacion
// Universitat Politecnica de Catalunya
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//


#include <iostream>
#include <fstream>
#include <sstream>
#include <memory>
#include <string.h>
#include <stdlib.h>
#include <zlib.h>
#include <vector>
#include <sys/time.h>
#include <time.h>
#include <math.h>
#include <algorithm>


#include "../../../../utils/CacheLRU.hpp"

#include "ConfigParameter.hpp"
#include "ConfigFile.h"
#include "constants.h"
#include "memory.h"
#include "trace.h"
#include "cacheCMP.h"
#include "SingleThreadCMP.h"
#include "tlbCMP.h"
#include "LocalPartitionCMP.h"
#include "output.h"


/*
 * Tellme usage explanation
 */
static void
usage(void)
{
    std::cout << "USAGE: MemoryTellme <config_file>"<<std::endl;
    std::cout << "MemoryTellme is a fast CMP memory simulator"<<std::endl;
}


void
tellmeLocalMem(Memory *mem,Trace *trace,ConfigParameter & conf)
{
    LocalPartitionCMP * cmp;
    std::cout<<"LocalPartition CMP threads: "<<mem->getNumThreads()
          <<" pageSize: "<<conf.getPageSize() <<" LineSize: "<<conf.getLineSize()
          <<" L0Size: "<<conf.getL0Size()<<" LMsize: "<<conf.getLMSize()
          <<" Invalidate L0 Policy:"<<conf.getL0InvalidatePolicy()<<std::endl;

    cmp = new LocalPartitionCMP(conf.getNumberChips(),conf.getNumberthreadsInChip(),
                                conf.getMultiFELIPolicy(),conf.getInterChipThreshold(),
                                mem->getNumThreads(),conf.getPageSize(),conf.getLineSize(),
                                conf.getL0Size(),mem->getLMSize(),conf.getThreshold(),
                                conf.getL0InvalidatePolicy(),conf.getVictimPolicy());
    trace->setParams(conf.getDirname(0).c_str(),mem->getNumThreads(),conf.getSkipWrite(),
                    false,conf.getTraceIterations());
    std::cout<<"Start CMP execution ..."<<endl;
    cmp->execute(mem,trace,conf);
    std::cout<<"End of CMP execution ..."<<endl;

    delete cmp;
}

void
tellmeCache(Memory *mem,Trace *trace,ConfigParameter & conf)
{

    // read traces
    cout<<"Init Simulation MemoryAccesses "<<endl;
    trace->setParams(conf.getDirname(0).c_str(),mem->getNumThreads(),conf.getSkipWrite(),
                    false,conf.getTraceIterations());
    // Initialice Cache Data Structures
    CacheCMP * cacheCMP = new CacheCMP(conf.getNumberChips(),
                              conf.getNumberthreadsInChip(),
                              mem->getNumThreads(),
                              conf.getLineSize(),trace->getpageSize(),conf.getL0Size(),
                              conf.getL1Size(),conf.getL2Size(),conf.getL2Shared(),
                              conf.getL1Prefetch());
    cacheCMP->executeCacheSimulation(mem,trace);
    outputCMPResults (mem,trace,cacheCMP,conf);
    cout <<"Simulation ended."<<endl;

    delete cacheCMP;

}

void
tellmeL0(Memory *mem,Trace *trace,ConfigParameter & conf)
{

    // read traces
    cout<<"Init Simulation L0 MemoryAccesses "<<endl;
    trace->setParams(conf.getDirname(0).c_str(),mem->getNumThreads(),conf.getSkipWrite(),
                     false,conf.getTraceIterations());
    // Initialice Cache Data Structures
    CacheCMP * cacheCMP = new CacheCMP(conf.getNumberChips(),
                                       conf.getNumberthreadsInChip(),
                                       mem->getNumThreads(),conf.getLineSize(),
                                       trace->getpageSize(),conf.getL0Size(),0,0,0,0);
    cacheCMP->executeL0Simulation(mem,trace,conf.getTid());
    outputL0 (trace,cacheCMP,conf.getTid());
    cout <<"Simulation ended."<<endl;

    delete cacheCMP;

}
void
tellmeTLB(ConfigParameter & conf)
{
    Trace **trace;
    trace = new Trace*[conf.getNumberApps()];

    for (int i=0;i<conf.getNumberApps();i++) {
        std::cout<<"New trace set params App : "<<conf.getDirname(i)<<
                   " Threads "<<conf.getThreads(i)<<" PageSize "<<conf.getPageSize()<<std::endl;
        trace[i] = new Trace(conf.getThreads(i),conf.getPageSize());
        trace[i]->setParams(conf.getDirname(i).c_str(),conf.getThreads(i),false,
                false,conf.getTraceIterations());
    }

    // read traces
    std::cout<<"Init TLB Simulation MemoryAccesses "<<endl;

    // Initialice Cache Data Structures
    TLBCMP * CMP = new TLBCMP(conf.getTotalThreads(),conf.getLineSize(),conf.getPageSize(),
                              conf.getPagesInRange(),conf.getTLB1Size(),conf.getTLB2Size(),
                              conf.getTLB1Assoc(),conf.getTLB2Assoc());
    for (int i=0;i<conf.getNumberApps();i++) {
        CMP->setAppTrace(trace[i],conf.getThreads(i));
    }

    std::cout << "CMP execute "<<std::endl;
    CMP->execute();
    outputTLBResults (conf.getDirname(0).c_str(),CMP);
    cout <<"Simulation ended."<<endl;

    for (int i=0;i<conf.getNumberApps();i++) {
        delete trace[i];
    }
    delete CMP;

}



void
tellmeSimulationMethodology(Memory *mem,Trace *trace,ConfigParameter & conf)
{
    // read traces
    cout<<"Init Simulation MemoryAccesses "<<endl;
    trace->setParams(conf.getDirname(0).c_str(),mem->getNumThreads(),conf.getSkipWrite(),
                     false,conf.getTraceIterations());
    // Initialice Cache Data Structures
    SingleThreadCMP * CMP =  new SingleThreadCMP(1,conf.getLineSize(),
                                    trace->getpageSize(),conf.getL1Size());
    CMP->setThread(2);
    CMP->executeCacheSimulation(mem,trace);
    outputSingleThreadCMPResults (trace,CMP);
    cout <<"Simulation ended."<<endl;
}



void
getCommonConfigurationParameters(ConfigFile &f,ConfigParameter & conf)
{
    std::cout << "getCommonParameters"<<std::endl;
    conf.setNumberApps(f.read<int>("apps"));
    conf.setNumberChips(f.read<int>("chips"));
    conf.setNumberThreadsInChip(f.read<int>("threadsInChip"));

    /* loop to obtain number of applications */
    for (int i=0;i<conf.getNumberApps();i++) {
        std::stringstream dirname;
        dirname << "appDirname" <<i;
        std::string appName=f.read<string>(dirname.str());
        conf.setDirname(appName);
        std::stringstream threads;
        threads<< "threads" <<i;
        conf.setThreads(f.read<int>(threads.str()));
    }

    conf.setOption(f.read<int>("option"));
    conf.setPageSize(f.read<int>("pageSize"));
    conf.setSkipWrite(f.read<int>("skipwrite")==1?true:false);
    conf.setTraceIterations(f.read<int>("traceIterations"));
}

void
getLMParameters(ConfigFile &f,ConfigParameter & conf)
{
    std::cout << "getLMParameters"<<std::endl;
    conf.setLMSize(f.read<int>("LMSize"));
    conf.setLMOption(f.read<int>("LMOption"));
    conf.setL0Size(f.read<int>("L0Size"));
    conf.setMultiFELIPolicy(f.read<int>("MultiFELIPolicy"));
    conf.setInterChipThreshold(f.read<int>("InterChipThreshold"));
}

void
getTLBParameters(ConfigFile &f,ConfigParameter & conf)
{
    std::cout << "getTLBParameters"<<std::endl;
    conf.setLineSize(f.read<int>("lineSize"));
    conf.setTLB1Size(f.read<int>("L1TLBSize"));
    conf.setTLB1Assoc(f.read<int>("L1TLBAssoc"));
    conf.setTLB2Size(f.read<int>("L2TLBSize"));
    conf.setTLB1Assoc(f.read<int>("L2TLBAssoc"));
    conf.setPagesInRange(f.read<int>("L2TLBAssoc"));
}

void
getCacheParameters(ConfigFile &f,ConfigParameter & conf)
{
    std::cout << "getCacheParameters"<<std::endl;
    conf.setL1Size(f.read<int>("L1Size"));
    conf.setL2Size(f.read<int>("L2Size"));
    conf.setL2Shared(((f.read<int>("L2Shared"))==1)?true:false);
    std::cout << "end"<<std::endl;
}

void
getMigrationParameters(ConfigFile &f,ConfigParameter & conf)
{

    std::cout << "getMigrationParameters"<<std::endl;
    conf.setMigrate(f.read<int>("migrate"));
    conf.setThreshold(f.read<int>("threshold"));
    conf.setL0InvalidatePolicy(f.read<int>("invalidatePolicy"));
    conf.setVictimPolicy(f.read<int>("victimPolicy"));
}

void
readParams(ConfigFile &f,ConfigParameter & conf)
{
    getCommonConfigurationParameters(f,conf);
    getTLBParameters(f,conf);
    switch (conf.getOption()) {
        case TELLME_CACHE:
            getCacheParameters(f,conf);
            break;
        case TELLME_LM:
            getLMParameters(f,conf);
            getMigrationParameters(f,conf);
            break;
        case TELLME_L0:
            // get Thread to Simulate
            break;
        case TELLME_SIMULATION:
            getCacheParameters(f,conf);
    }
}

int
main(int argc,char *argv[])
{
    Memory *mem=NULL;
    Trace *trace=NULL;

    if (argc<1) {
        usage();
        return -1;
    }

    /* Read Configuration file from Parameters */
    ConfigFile configFile(argv[1]);
    ConfigParameter conf;
    readParams(configFile,conf);

    /* Initialize global objects */
    srand(69);
    trace = new Trace(conf.getTotalThreads(),conf.getPageSize());
    mem = new Memory(conf.getTotalThreads(),conf.getPageSize(),conf.getLMSize());
    if (trace==NULL || mem==NULL) {
        FATAL("Unable to allocate memory for trace/mem\n");
    }
    outputSimulationDescription(conf);

    /* Simulation type depends on Mem.Hierarchy */
    switch (conf.getOption()) {
        case TELLME_CACHE:
            /* Simulate a cache based CMP */
            tellmeCache(mem,trace,conf);
            break;
        case TELLME_LM:
            /* Simulate a scratchpad based CMP */
            mem->setMigrate(conf.getMigrate());
            tellmeLocalMem(mem,trace,conf);
            break;
        case TELLME_TLB:
            tellmeTLB(conf);
            break;
        case TELLME_L0:
            tellmeL0(mem,trace,conf);
            break;
        case TELLME_SIMULATION:
            tellmeSimulationMethodology(mem,trace,conf);
            break;
        default:
            usage();
    }

    delete mem;
    delete trace;

    return 1;
}
